package com.hsgene.user.session;

import com.hsgene.common.util.basic.UUID32;
import com.hsgene.common.util.redis.RedisConstants;
import com.hsgene.common.util.result.ApiResult;
import com.hsgene.common.util.security.MD5Utils;
import com.hsgene.common.util.web.session.SessionConstants;
import com.hsgene.common.util.web.session.UserInfo;
import com.hsgene.common.util.web.session.UserSession;
import com.hsgene.user.constants.CustomerSourceType;
import com.hsgene.user.exception.UserErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

/**
 * 自定义用户Session管理组件
 *
 * @author wangbing
 * @version 1.0, 2018/7/4
 */
@Component
public class UserSessionComponent {

    private final static Logger logger = LoggerFactory.getLogger(UserSessionComponent.class);

    @Value("${user.session.httpSessionEnable}")
    private String httpSessionEnable;

    @Resource
    private RedisTemplate<String, UserSession> redisTemplate;

    /**
     * 添加用户会话信息
     *
     * @param httpSession HttpSession
     * @param userInfo    用户信息
     * @return UserSession
     */
    public UserSession add(HttpSession httpSession, UserInfo userInfo) {
        return addOrUpdate(httpSession, userInfo, null);
    }

    /**
     * 刷新用户信息
     *
     * @param userInfo 用户信息
     * @return 刷新是否成功
     */
    public boolean refreshUserInfo(UserInfo userInfo) {
        String redisSessionKey = getRedisSessionKey(userInfo);
        UserSession session = redisTemplate.opsForValue().get(redisSessionKey);
        if (session == null) {
            logger.warn("刷新用户会话信息失败！cause: 用户不存在. userInfo = {}", userInfo);
            return false;
        }
        session.setUserInfo(userInfo);
        redisTemplate.opsForValue().set(redisSessionKey, session);

        return true;
    }

    /**
     * 刷新用户AccessToken
     *
     * @param httpSession  HttpSession
     * @param sessionId    用户sessionId
     * @param refreshToken 刷新Token令牌
     * @return 刷新结果
     */
    public ApiResult<UserSession> updateAccessToken(HttpSession httpSession, String sessionId, String refreshToken) {
        UserSession session = redisTemplate.opsForValue().get(getRedisSessionKey(sessionId));
        if (session == null) {
            logger.info("更新AccessToken失败！cause: 用户不存在." +
                    " sessionId = {}, refreshToken = {}", sessionId, refreshToken);
            return ApiResult.fail(UserErrorCode.USER_NOT_LOGIN);
        }
        if (!session.isAccessTokenExpired()) {
            logger.info("更新AccessToken失败！cause: 未到刷新时间." +
                    " sessionId = {}, refreshToken = {}", sessionId, refreshToken);
            return ApiResult.fail(UserErrorCode.USER_TOKEN_CANNOT_REFRESH);
        }
        if (!session.getRefreshToken().equals(refreshToken)) {
            logger.info("更新AccessToken失败！cause: 刷新令牌错误." +
                    " sessionId = {}, refreshToken = {}", sessionId, refreshToken);
            return ApiResult.fail(UserErrorCode.USER_NOT_LOGIN);
        }

        return ApiResult.succ(addOrUpdate(httpSession, session.getUserInfo(), session));
    }

    /**
     * 删除用户会话信息-用户退出登录
     *
     * @param httpSession HttpSession
     * @param userInfo    用户信息
     */
    public void remove(HttpSession httpSession, UserInfo userInfo) {
        if (userInfo != null) {
            redisTemplate.delete(getRedisSessionKey(userInfo));
            if (isHttpSessionEnable() && httpSession != null) {
                httpSession.removeAttribute(SessionConstants.SESSION_ID_KEY);
                httpSession.removeAttribute(SessionConstants.ACCESS_TOKEN_KEY);
            }
        }
    }

    /**
     * 添加用户更新用户会话信息
     *
     * @param httpSession HttpSession
     * @param userInfo    用户信息
     * @param oldSession  旧的用户会话信息
     * @return UserSession
     */
    private UserSession addOrUpdate(HttpSession httpSession, UserInfo userInfo, UserSession oldSession) {
        UserSession session = new UserSession();
        // 构造session_id、access_token，refresh_token
        session.setSessionId(userInfo.getSessionId());
        session.setAccessToken(MD5Utils.getMD5Code(userInfo.getUserId()
                + userInfo.getUserType() + UUID32.randomUUIDString()));
        session.setRefreshToken(MD5Utils.getMD5Code(userInfo.getUserType()
                + userInfo.getUserId() + UUID32.randomUUIDString()));
        session.setUserInfo(userInfo);
        // 设置access_token过期时间
        if (UserSession.ACCESS_TOKEN_EXPIRE_TIME > 0) {
            LocalDateTime expireTime = LocalDateTime.now().plus(UserSession.ACCESS_TOKEN_EXPIRE_TIME, ChronoUnit.MINUTES);
            session.setExpireTime(expireTime);
        }
        // 设置旧的access_token
        if (oldSession != null) {
            session.setOldAccessToken(oldSession.getAccessToken());
        }
        // 将会话信息存入Redis
        saveSession(session);
        // 将会话信息写入HttpSession中
        if (isHttpSessionEnable()) {
            logger.info("将用户会话信息写入HttpSession");
            httpSession.setAttribute(SessionConstants.SESSION_ID_KEY, session.getSessionId());
            httpSession.setAttribute(SessionConstants.ACCESS_TOKEN_KEY, session.getAccessToken());
        }
        // session中用户信息、旧access_token、access_token过期时间不返回前端
        session.setUserInfo(null);
        session.setOldAccessToken(null);
        session.setExpireTime(null);
        return session;
    }

    public void saveSession(UserSession session) {
        // 将会话信息存入Redis
        if (session.getUserInfo() != null && "0".equals(session.getUserInfo().getUserType())) {
            // 运营后台缓存用户信息设置过期时间
            redisTemplate.opsForValue().set(getRedisSessionKey(session), session, UserSession.ACCESS_TOKEN_EXPIRE_TIME + 60L, TimeUnit.MINUTES);
        } else {
            redisTemplate.opsForValue().set(getRedisSessionKey(session), session);
        }
    }

    /**
     * 获取session_id
     *
     * @param userInfo 用户信息
     * @return session_id
     */
    public static String buildSessionId(UserInfo userInfo) {
        return userInfo.getUserType() + SessionConstants.SESSION_KEY_SPLIT + userInfo.getUserId();
    }

    /**
     * 当用户多端登录时，为每个端生成不同的session_id，避免登录登出相互影响
     *
     * @param sourceType 登录端别
     * @param userInfo   用户信息
     * @return session_id
     */
    public static String buildCustomerSessionId(CustomerSourceType sourceType, UserInfo userInfo) {
        String sessionId = buildSessionId(userInfo);
        if (sourceType != null) {
            if (CustomerSourceType.WEB_CONSOLE.equals(sourceType)) {
                sessionId = sourceType.getSessionIdPrefix() + sessionId + SessionConstants.SESSION_KEY_SPLIT + UUID32.randomUUIDString();
            } else {
                sessionId = sourceType.getSessionIdPrefix() + sessionId;
            }

        }
        return sessionId;
    }

    /**
     * 获取Redis缓存Session键
     *
     * @param userInfo 用户信息
     * @return Redis缓存Session键
     */
    private String getRedisSessionKey(UserInfo userInfo) {
        return getRedisSessionKey(userInfo.getSessionId());
    }

    /**
     * 获取Redis缓存Session键
     *
     * @param session 用户会话信息
     * @return Redis缓存Session键
     */
    private String getRedisSessionKey(UserSession session) {
        return RedisConstants.DEFAULT_REDIS_PREFIX + RedisConstants.COLON_SPLIT + SessionConstants.REDIS_SESSION_KEY_PREFIX + session.getSessionId();
    }

    /**
     * 获取Redis缓存Session键
     *
     * @param sessionId 用户sessionId
     * @return Redis缓存Session键
     */
    public static String getRedisSessionKey(String sessionId) {
        return RedisConstants.DEFAULT_REDIS_PREFIX + RedisConstants.COLON_SPLIT + SessionConstants.REDIS_SESSION_KEY_PREFIX + sessionId;
    }

    /**
     * 是否将用户会话信息写入HttpSession
     *
     * @return true：要将用户会话信息写入
     */
    private boolean isHttpSessionEnable() {
        return Boolean.TRUE.toString().equalsIgnoreCase((httpSessionEnable));
    }
}
